//
//  PSScorerTests.m
//  PrefCore
//
//  Created by Sergey on 3/4/14.
//  Copyright (c) 2014 Play-on-smart. All rights reserved.
//

#import <XCTest/XCTest.h>
#import "PSScorer.h"
#import "PSCalculator.h"

@interface PSScorerTests : XCTestCase {
    PSGame *game;
    PSGameSettings *settings;
}

@end

@implementation PSScorerTests

-(void)setUp {
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
    [PSGameSettings clearUserDefaults:ud];
    [ud synchronize];
    settings = [PSGameSettings settingsFromUserDefaults:ud];
    settings.poolSize = 30;
    game = [[PSGame alloc] init];
}

-(void)testInstances {
    XCTAssertNotNil([PSScorer passingScorer]);
    XCTAssertNotNil([PSScorer passingRostovScorer]);
    XCTAssertNotNil([PSScorer misereScorer]);
    XCTAssertNotNil([PSScorer trickingScorer]);
    XCTAssertNotNil([PSScorer trickingHalfWhistScorer]);
    XCTAssertNotNil([PSScorer trickingTenScorer]);
    XCTAssertNotNil([PSScorer trickingTwoPassesScorer]);
    XCTAssertNotNil([PSScorer trickingWithoutThreeScorer]);
}

-(void)testPassingTrickCosts1111 {
    settings.passingProgression = kPassingProgression_1111;
    settings.passingTrickCost = kPassingTrickCost_1;
    for (int i = 0 ; i != 100 ; i++) {
        PSScoreValue cost = [PSPassingScorer trickCostsForGame:game withSettings:settings];
        XCTAssertEqual(cost, 1);
        [game incrementConsecutivePassingSets];
    }
    settings.passingTrickCost = kPassingTrickCost_2;
    [game resetConsecutivePassingSets];
    for (int i = 0 ; i != 100 ; i++) {
        PSScoreValue cost = [PSPassingScorer trickCostsForGame:game withSettings:settings];
        XCTAssertEqual(cost, 2);
        [game incrementConsecutivePassingSets];
    }
}

-(void)testPassingTrickCosts123123 {
    settings.passingProgression = kPassingProgression_123123;
    settings.passingTrickCost = kPassingTrickCost_1;
    for (int i = 0 ; i != 100 ; i++) {
        PSScoreValue cost = [PSPassingScorer trickCostsForGame:game withSettings:settings];
        XCTAssertEqual(cost, i % 3 + 1);
        [game incrementConsecutivePassingSets];
    }
    settings.passingTrickCost = kPassingTrickCost_2;
    [game resetConsecutivePassingSets];
    for (int i = 0 ; i != 100 ; i++) {
        PSScoreValue cost = [PSPassingScorer trickCostsForGame:game withSettings:settings];
        XCTAssertEqual(cost, 2 * (i % 3 + 1));
        [game incrementConsecutivePassingSets];
    }
}

-(void)testPassingTrickCosts1233 {
    settings.passingProgression = kPassingProgression_1233;
    settings.passingTrickCost = kPassingTrickCost_1;
    for (int i = 0 ; i != 100 ; i++) {
        PSScoreValue cost = [PSPassingScorer trickCostsForGame:game withSettings:settings];
        XCTAssertEqual(cost, i < 2 ? i + 1 : 3);
        [game incrementConsecutivePassingSets];
    }
    settings.passingTrickCost = kPassingTrickCost_2;
    [game resetConsecutivePassingSets];
    for (int i = 0 ; i != 100 ; i++) {
        PSScoreValue cost = [PSPassingScorer trickCostsForGame:game withSettings:settings];
        XCTAssertEqual(cost, i < 2 ? 2 * i + 2 : 6);
        [game incrementConsecutivePassingSets];
    }
}

-(void)testPassingTrickCosts1244 {
    settings.passingProgression = kPassingProgression_1244;
    settings.passingTrickCost = kPassingTrickCost_1;
    for (int i = 0 ; i != 100 ; i++) {
        PSScoreValue cost = [PSPassingScorer trickCostsForGame:game withSettings:settings];
        XCTAssertEqual(cost, i < 2 ? i + 1 : 4);
        [game incrementConsecutivePassingSets];
    }
    settings.passingTrickCost = kPassingTrickCost_2;
    [game resetConsecutivePassingSets];
    for (int i = 0 ; i != 100 ; i++) {
        PSScoreValue cost = [PSPassingScorer trickCostsForGame:game withSettings:settings];
        XCTAssertEqual(cost, i < 2 ? 2 * i + 2 : 8);
        [game incrementConsecutivePassingSets];
    }
}

-(void)testPassingTrickCosts1234 {
    settings.passingProgression = kPassingProgression_1234;
    settings.passingTrickCost = kPassingTrickCost_1;
    for (int i = 0 ; i != 100 ; i++) {
        PSScoreValue cost = [PSPassingScorer trickCostsForGame:game withSettings:settings];
        XCTAssertEqual(cost, i + 1);
        [game incrementConsecutivePassingSets];
    }
    settings.passingTrickCost = kPassingTrickCost_2;
    [game resetConsecutivePassingSets];
    for (int i = 0 ; i != 100 ; i++) {
        PSScoreValue cost = [PSPassingScorer trickCostsForGame:game withSettings:settings];
        XCTAssertEqual(cost, 2 * i + 2);
        [game incrementConsecutivePassingSets];
    }
}

-(void)testPassingTrickCosts1248 {
    settings.passingProgression = kPassingProgression_1248;
    settings.passingTrickCost = kPassingTrickCost_1;
    for (int i = 0 ; i != 100 ; i++) {
        PSScoreValue cost = [PSPassingScorer trickCostsForGame:game withSettings:settings];
        XCTAssertEqual(cost, 1 << MIN(i, 8));
        [game incrementConsecutivePassingSets];
    }
    settings.passingTrickCost = kPassingTrickCost_2;
    [game resetConsecutivePassingSets];
    for (int i = 0 ; i != 100 ; i++) {
        PSScoreValue cost = [PSPassingScorer trickCostsForGame:game withSettings:settings];
        XCTAssertEqual(cost, 1 << MIN(i+1, 9));
        [game incrementConsecutivePassingSets];
    }
}

-(void)testMisereScorer {
    PSScorer *scorer = [PSScorer misereScorer];
    PSCalculator *calc = [PSCalculator calculatorForGameFlavor:kGameFlavorSochi];
    for (int d = 0 ; d != 3 ; d++) {
        game = [[PSGame alloc] init];
        game.player.declarer = d;
        // No tricks
        [scorer updateGame:game withSettings:settings andCalculator:calc];
        NSLog(@"%lld", [game scoreForPlayerId:d].pool.current);
        XCTAssertEqual([game scoreForPlayerId:d].pool.current, 10);
        [scorer updateStatisticsOfGame:game];
        XCTAssertEqual([game.statistics misere][d].success, 1);
        XCTAssertEqual([game.statistics misere][d].sets, 1);
        XCTAssertEqual([game.statistics misere][d].tricks, 0);
        for (int t = 1 ; t <= 10 ; t++) {
            game = [[PSGame alloc] init];
            game.player.declarer = d;
            game.player.tricks[d] = t;
            [scorer updateGame:game withSettings:settings andCalculator:calc];
            XCTAssertEqual([game scoreForPlayerId:d].mountain.current, 10*t);
            [scorer updateStatisticsOfGame:game];
            XCTAssertEqual([game.statistics misere][d].sets, 1);
            XCTAssertEqual([game.statistics misere][d].success, 0);
            XCTAssertEqual([game.statistics misere][d].tricks, t);
        }
    }
}

-(void)testPassingScorer {
    PSScorer *scorer = [PSScorer passingScorer];
    PSCalculator *calc = [PSCalculator calculatorForGameFlavor:kGameFlavorSochi];
    settings.passingProgression = kPassingProgression_1111;
    PSTrickCount tricks[PS_GAME_PLAYERS];
    for (tricks[0] = 0 ; tricks[0] <= PS_BID_MAX ; tricks[0]++) {
        for (tricks[1] = 0 ; tricks[0]+tricks[1] <= PS_BID_MAX ; tricks[1]++) {
            tricks[2] = PS_BID_MAX - tricks[0] - tricks[1];
            PSTrickCount min = MIN(tricks[0], MIN(tricks[1], tricks[2]));
            game = [[PSGame alloc] init];
            for (PSPlayerId i = 0 ; i != PS_GAME_PLAYERS ; i++) {
                game.player.tricks[i] = tricks[i];
            }
            [scorer updateGame:game withSettings:settings andCalculator:calc];
            for (PSPlayerId i = 0 ; i != PS_GAME_PLAYERS ; i++) {
                if (tricks[i]) {
                    XCTAssertEqual(tricks[i] - min, [game scoreForPlayerId:i].mountain.current);
                } else {
                    XCTAssertEqual(1, [game scoreForPlayerId:i].pool.current);
                }
            }
            [scorer updateStatisticsOfGame:game];
            for (PSPlayerId i = 0 ; i != PS_GAME_PLAYERS ; i++) {
                XCTAssertEqual(tricks[i], [game.statistics passingTricks][i]);
            }
        }
    }
}

-(void)testPassingScorerRostov {
    PSScorer *scorer = [PSScorer passingRostovScorer];
    PSCalculator *calc = [PSCalculator calculatorForGameFlavor:kGameFlavorRostov];
    PSTrickCount tricks[PS_GAME_PLAYERS];
    for (tricks[0] = 0 ; tricks[0] <= PS_BID_MAX ; tricks[0]++) {
        for (tricks[1] = 0 ; tricks[0]+tricks[1] <= PS_BID_MAX ; tricks[1]++) {
            tricks[2] = PS_BID_MAX - tricks[0] - tricks[1];
            PSTrickCount min = MIN(tricks[0], MIN(tricks[1], tricks[2]));
            game = [[PSGame alloc] init];
            int winnerCount = 0;
            for (PSPlayerId i = 0 ; i != PS_GAME_PLAYERS ; i++) {
                game.player.tricks[i] = tricks[i];
                winnerCount += (tricks[i] == min);
            }
            [scorer updateGame:game withSettings:settings andCalculator:calc];
            for (PSPlayerId i = 0 ; i != PS_GAME_PLAYERS ; i++) {
                if (tricks[i] == min) {
                    // This player has won the round of passes
                    for (int j = 0 ; j != PS_GAME_PLAYERS ; j++) {
                        if (tricks[j] != min) {
                            // j has lost
                            XCTAssertEqual(
                                PS_ROSTOV_PASSING_TRICK_COST * tricks[j] / winnerCount
                            ,   [game scoreForPlayerId:i].whists[j].current
                            );
                        } else {
                            // j is the same player or is another winner
                            XCTAssertEqual(0, [game scoreForPlayerId:i].whists[j].current);
                        }
                    }
                    if (!tricks[i]) {
                        // Winners who take no tricks get an extra point in the pool
                        XCTAssertEqual(1, [game scoreForPlayerId:i].pool.current);
                    }
                } else {
                    // This player has lost the round of passes
                    XCTAssertEqual(0, [game scoreForPlayerId:i].pool.current);
                    XCTAssertEqual(0, [game scoreForPlayerId:i].whists[0].current);
                    XCTAssertEqual(0, [game scoreForPlayerId:i].whists[1].current);
                    XCTAssertEqual(0, [game scoreForPlayerId:i].whists[2].current);
                }
            }
            [scorer updateStatisticsOfGame:game];
            for (PSPlayerId i = 0 ; i != PS_GAME_PLAYERS ; i++) {
                XCTAssertEqual(tricks[i], [game.statistics passingTricks][i]);
            }
        }
    }
}

-(void)testTrickingScorerOneOpponentSuccess {
    PSScorer *scorer = [PSScorer trickingScorer];
    PSCalculator *calc = [PSCalculator calculatorForGameFlavor:kGameFlavorSochi];
    for (PSTrickCount bidTricks = PS_BID_MIN ; bidTricks <= PS_BID_MAX ; bidTricks++) {
        for (PSPlayerId decl = 0 ; decl != 3 ; decl++) {
            for (PSPlayerId opp = 0 ; opp != 3 ; opp++) {
                if (decl == opp) {
                    continue;
                }
                PSPlayerId passer = 3-decl-opp;
                for (PSTrickCount actualTricks = bidTricks ; actualTricks <= PS_BID_MAX ; actualTricks++) {
                    game = [[PSGame alloc] init];
                    game.player.declarer = decl;
                    game.player.opponent = opp;
                    game.whisters = 1;
                    [game setCurrentBid:[PSBid bidWhist] forPlayer:opp];
                    [game setCurrentBid:[PSBid bidPass] forPlayer:passer];
                    [game setCurrentBid:[PSBid bidPlayWithTrump:kSuitClubs tricks:bidTricks] forPlayer:decl];
                    game.player.tricks[decl] = actualTricks;
                    game.player.tricks[opp] = PS_BID_MAX - actualTricks; // Opponent takes the rest
                    [scorer updateGame:game withSettings:settings andCalculator:calc];
                    PSScoreValue cost = 2*(PSGetContractIndex(bidTricks)+1);
                    XCTAssertEqual(cost, [game scoreForPlayerId:decl].pool.current);
                    XCTAssertEqual(cost * (PS_BID_MAX-actualTricks), [game scoreForPlayerId:opp].whists[decl].current);
                    [scorer updateStatisticsOfGame:game];
                    PSAggregationStatistics *agg = [game.statistics trickingOfPlayer:decl forContract:bidTricks];
                    XCTAssertEqual(actualTricks, agg.tricks);
                    XCTAssertEqual(1, agg.sets);
                    XCTAssertEqual(1, agg.success);
                    XCTAssertEqual(1, [game.statistics passes][passer]);
                }
            }
        }
    }
}

-(void)testTrickingScorerOneOpponentFailureGentleman {
    PSScorer *scorer = [PSScorer trickingScorer];
    PSCalculator *calc = [PSCalculator calculatorForGameFlavor:kGameFlavorSochi];
    settings.whistType = kWhistGentleman;
    for (PSTrickCount bidTricks = PS_BID_MIN ; bidTricks <= PS_BID_MAX ; bidTricks++) {
        for (PSPlayerId decl = 0 ; decl != 3 ; decl++) {
            for (PSPlayerId opp = 0 ; opp != 3 ; opp++) {
                if (decl == opp) {
                    continue;
                }
                PSPlayerId passer = 3 - decl - opp;
                for (PSTrickCount actualTricks = 0 ; actualTricks < bidTricks ; actualTricks++) {
                    game = [[PSGame alloc] init];
                    game.player.declarer = decl;
                    game.player.opponent = opp;
                    game.whisters = 1;
                    game.player.passer = passer;
                    [game setCurrentBid:[PSBid bidPlayWithTrump:kSuitClubs tricks:bidTricks] forPlayer:decl];
                    [game setCurrentBid:[PSBid bidWhist] forPlayer:opp];
                    [game setCurrentBid:[PSBid bidPass] forPlayer:passer];
                    game.player.tricks[decl] = actualTricks;
                    game.player.tricks[opp] = PS_BID_MAX - actualTricks; // Opponent takes the rest
                    [scorer updateGame:game withSettings:settings andCalculator:calc];
                    PSScoreValue halfCost = (PSGetContractIndex(bidTricks)+1);
                    XCTAssertEqual(2 * halfCost * (bidTricks-actualTricks), [game scoreForPlayerId:decl].mountain.current);
                    PSScoreValue whistTotal = halfCost * (2*(bidTricks-actualTricks) + (PS_BID_MAX-actualTricks));
                    XCTAssertEqual(whistTotal, [game scoreForPlayerId:opp].whists[decl].current);
                    XCTAssertEqual(whistTotal, [game scoreForPlayerId:passer].whists[decl].current);
                    [scorer updateStatisticsOfGame:game];
                    PSAggregationStatistics *agg = [game.statistics trickingOfPlayer:decl forContract:bidTricks];
                    XCTAssertEqual(actualTricks, agg.tricks);
                    XCTAssertEqual(1, agg.sets);
                    XCTAssertEqual(0, agg.success);
                    XCTAssertEqual(1, [game.statistics passes][passer]);
                }
            }
        }
    }
}

-(void)testTrickingScorerOneOpponentFailureGreedy {
    PSScorer *scorer = [PSScorer trickingScorer];
    PSCalculator *calc = [PSCalculator calculatorForGameFlavor:kGameFlavorSochi];
    for (PSTrickCount bidTricks = PS_BID_MIN ; bidTricks <= PS_BID_MAX ; bidTricks++) {
        for (PSPlayerId decl = 0 ; decl != 3 ; decl++) {
            for (PSPlayerId opp = 0 ; opp != 3 ; opp++) {
                if (decl == opp) {
                    continue;
                }
                PSPlayerId passer = 3 - decl - opp;
                for (PSTrickCount actualTricks = 0 ; actualTricks < bidTricks ; actualTricks++) {
                    game = [[PSGame alloc] init];
                    game.player.declarer = decl;
                    game.player.opponent = opp;
                    game.whisters = 1;
                    game.player.passer = passer;
                    [game setCurrentBid:[PSBid bidPlayWithTrump:kSuitClubs tricks:bidTricks] forPlayer:decl];
                    [game setCurrentBid:[PSBid bidWhist] forPlayer:opp];
                    [game setCurrentBid:[PSBid bidPass] forPlayer:passer];
                    game.player.tricks[decl] = actualTricks;
                    game.player.tricks[opp] = PS_BID_MAX - actualTricks; // Opponent takes the rest
                    [scorer updateGame:game withSettings:settings andCalculator:calc];
                    PSScoreValue cost = 2*(PSGetContractIndex(bidTricks)+1);
                    XCTAssertEqual(cost * (bidTricks-actualTricks), [game scoreForPlayerId:decl].mountain.current);
                    XCTAssertEqual(cost * (PS_BID_MAX+bidTricks-2*actualTricks), [game scoreForPlayerId:opp].whists[decl].current);
                    XCTAssertEqual(cost * (bidTricks-actualTricks), [game scoreForPlayerId:passer].whists[decl].current);
                    [scorer updateStatisticsOfGame:game];
                    PSAggregationStatistics *agg = [game.statistics trickingOfPlayer:decl forContract:bidTricks];
                    XCTAssertEqual(actualTricks, agg.tricks);
                    XCTAssertEqual(1, agg.sets);
                    XCTAssertEqual(0, agg.success);
                    XCTAssertEqual(1, [game.statistics passes][3-decl-opp]);
                }
            }
        }
    }
}

-(void)testTrickingScorerTwoOpponentsSuccess {
    PSScorer *scorer = [PSScorer trickingScorer];
    PSCalculator *calc = [PSCalculator calculatorForGameFlavor:kGameFlavorSochi];
    for (PSTrickCount bidTricks = PS_BID_MIN ; bidTricks <= PS_BID_MAX ; bidTricks++) {
        for (PSPlayerId decl = 0 ; decl != 3 ; decl++) {
            PSPlayerId opp1 = (decl+1) % PS_GAME_PLAYERS;
            PSPlayerId opp2 = (decl+2) % PS_GAME_PLAYERS;
            for (PSTrickCount actualTricks = bidTricks ; actualTricks <= PS_BID_MAX ; actualTricks++) {
                game = [[PSGame alloc] init];
                game.player.declarer = decl;
                game.whisters = 2;
                [game setCurrentBid:[PSBid bidPlayWithTrump:kSuitClubs tricks:bidTricks] forPlayer:decl];
                [game setCurrentBid:[PSBid bidWhist] forPlayer:opp1];
                [game setCurrentBid:[PSBid bidWhist] forPlayer:opp2];
                game.player.tricks[decl] = actualTricks;
                if (decl) {
                    // Swap opponents to exercise both branches
                    PSPlayerId tmp = opp1;
                    opp1 = opp2;
                    opp2 = tmp;
                }
                PSTrickCount remaining = PS_BID_MAX - actualTricks;
                PSTrickCount oppTricks = remaining > 1 ? remaining-1 : remaining;
                PSTrickCount lastTricks = PS_BID_MAX - actualTricks - oppTricks;
                game.player.tricks[opp1] = oppTricks;
                game.player.tricks[opp2] = lastTricks;
                [scorer updateGame:game withSettings:settings andCalculator:calc];
                PSScoreValue cost = 2*(PSGetContractIndex(bidTricks)+1);
                XCTAssertEqual(cost, [game scoreForPlayerId:decl].pool.current);
                XCTAssertEqual(cost * oppTricks, [game scoreForPlayerId:opp1].whists[decl].current);
                XCTAssertEqual(cost * lastTricks, [game scoreForPlayerId:opp2].whists[decl].current);
                [scorer updateStatisticsOfGame:game];
                PSAggregationStatistics *agg = [game.statistics trickingOfPlayer:decl forContract:bidTricks];
                XCTAssertEqual(actualTricks, agg.tricks);
                XCTAssertEqual(1, agg.sets);
                XCTAssertEqual(1, agg.success);
            }
        }
    }
}

-(void)testTrickingScorerTwoOpponentsFailure {
    PSScorer *scorer = [PSScorer trickingScorer];
    PSCalculator *calc = [PSCalculator calculatorForGameFlavor:kGameFlavorSochi];
    for (PSTrickCount bidTricks = PS_BID_MIN ; bidTricks <= PS_BID_MAX ; bidTricks++) {
        for (PSPlayerId decl = 0 ; decl != 3 ; decl++) {
            PSPlayerId opp1 = (decl+1) % PS_GAME_PLAYERS;
            PSPlayerId opp2 = (decl+2) % PS_GAME_PLAYERS;
            for (PSTrickCount actualTricks = 0 ; actualTricks < bidTricks ; actualTricks++) {
                game = [[PSGame alloc] init];
                game.player.declarer = decl;
                game.whisters = 2;
                [game setCurrentBid:[PSBid bidPlayWithTrump:kSuitClubs tricks:bidTricks] forPlayer:decl];
                [game setCurrentBid:[PSBid bidWhist] forPlayer:opp1];
                [game setCurrentBid:[PSBid bidWhist] forPlayer:opp2];
                game.player.tricks[decl] = actualTricks;
                game.player.tricks[opp1] = PS_BID_MAX - actualTricks; // One opponent takes the rest
                [scorer updateGame:game withSettings:settings andCalculator:calc];
                PSScoreValue cost = 2*(PSGetContractIndex(bidTricks)+1);
                XCTAssertEqual(cost * (bidTricks-actualTricks), [game scoreForPlayerId:decl].mountain.current);
                XCTAssertEqual(cost * (PS_BID_MAX+bidTricks-2*actualTricks), [game scoreForPlayerId:opp1].whists[decl].current);
                XCTAssertEqual(cost * (bidTricks-actualTricks), [game scoreForPlayerId:opp2].whists[decl].current);
                [scorer updateStatisticsOfGame:game];
                PSAggregationStatistics *agg = [game.statistics trickingOfPlayer:decl forContract:bidTricks];
                XCTAssertEqual(actualTricks, agg.tricks);
                XCTAssertEqual(1, agg.sets);
                XCTAssertEqual(0, agg.success);
            }
        }
    }
}

-(void)testTrickingTenScorer {
    PSScorer *scorer = [PSScorer trickingTenScorer];
    PSCalculator *calc = [PSCalculator calculatorForGameFlavor:kGameFlavorSochi];
    for (PSPlayerId decl = 0 ; decl != 3 ; decl++) {
        game = [[PSGame alloc] init];
        game.player.declarer = decl;
        [game setCurrentBid:[PSBid bidPlayWithTrump:kSuitClubs tricks:PS_BID_MAX] forPlayer:decl];
        game.player.tricks[decl] = PS_BID_MAX;
        // Take all required tricks
        [scorer updateGame:game withSettings:settings andCalculator:calc];
        XCTAssertEqual(10, [game scoreForPlayerId:decl].pool.current);
        [scorer updateStatisticsOfGame:game];
        PSAggregationStatistics *agg = [game.statistics trickingOfPlayer:decl forContract:PS_BID_MAX];
        XCTAssertEqual(PS_BID_MAX, agg.tricks);
        XCTAssertEqual(1, agg.sets);
        XCTAssertEqual(1, agg.success);
        // Take fewer tricks than is required by the contract
        for (PSTrickCount tricks = 0 ; tricks < PS_BID_MAX ; tricks++) {
            game = [[PSGame alloc] init];
            game.player.declarer = decl;
            [game setCurrentBid:[PSBid bidPlayWithTrump:kSuitClubs tricks:PS_BID_MAX] forPlayer:decl];
            game.player.tricks[decl] = tricks;
            [scorer updateGame:game withSettings:settings andCalculator:calc];
            XCTAssertEqual(10*(PS_BID_MAX-tricks), [game scoreForPlayerId:decl].mountain.current);
            [scorer updateStatisticsOfGame:game];
            PSAggregationStatistics *agg = [game.statistics trickingOfPlayer:decl forContract:PS_BID_MAX];
            XCTAssertEqual(tricks, agg.tricks);
            XCTAssertEqual(1, agg.sets);
            XCTAssertEqual(0, agg.success);
            for (PSPlayerId other = 0 ; other != 3 ; other++) {
                if (decl == other) {
                    continue;
                }
                XCTAssertEqual(10*(PS_BID_MAX-tricks), [game scoreForPlayerId:other].whists[decl].current);
            }
        }
    }
}

-(void)testTrickingHalfWhistScorer {
    PSScorer *scorer = [PSScorer trickingHalfWhistScorer];
    PSCalculator *calc = [PSCalculator calculatorForGameFlavor:kGameFlavorSochi];
    // Try only siz and seven, because half-whist is not allowed for games of eight and up
    for (PSTrickCount tricks = PS_BID_MIN ; tricks <= 7 ; tricks++) {
        for (PSPlayerId decl = 0 ; decl != 3 ; decl++) {
            for (PSPlayerId opp = 0 ; opp != 3 ; opp++) {
                if (decl == opp) {
                    continue;
                }
                PSPlayerId passer = 3-decl-opp;
                game = [[PSGame alloc] init];
                game.player.declarer = decl;
                game.player.half = opp;
                [game setCurrentBid:[PSBid bidPlayWithTrump:kSuitClubs tricks:tricks] forPlayer:decl];
                [game setCurrentBid:[PSBid bidHalfWhist] forPlayer:opp];
                [game setCurrentBid:[PSBid bidPass] forPlayer:passer];
                [scorer updateGame:game withSettings:settings andCalculator:calc];
                XCTAssertEqual(2*(PSGetContractIndex(tricks)+1), [game scoreForPlayerId:decl].pool.current);
                XCTAssertEqual(4, [game scoreForPlayerId:opp].whists[decl].current);
                [scorer updateStatisticsOfGame:game];
                PSAggregationStatistics *agg = [game.statistics trickingOfPlayer:decl forContract:tricks];
                XCTAssertEqual(tricks, agg.tricks);
                XCTAssertEqual(1, agg.sets);
                XCTAssertEqual(1, agg.success);
                XCTAssertEqual(1, [game.statistics passes][passer]);
            }
        }
    }
}

-(void)testTrickingTwoPassesScorer {
    PSScorer *scorer = [PSScorer trickingTwoPassesScorer];
    PSCalculator *calc = [PSCalculator calculatorForGameFlavor:kGameFlavorSochi];
    for (PSTrickCount tricks = PS_BID_MIN ; tricks <= PS_BID_MAX ; tricks++) {
        game = [[PSGame alloc] init];
        game.player.declarer = 0;
        [game setCurrentBid:[PSBid bidPlayWithTrump:kSuitClubs tricks:tricks] forPlayer:0];
        [scorer updateGame:game withSettings:settings andCalculator:calc];
        XCTAssertEqual(2*(PSGetContractIndex(tricks)+1), [game scoreForPlayerId:0].pool.current);
        [scorer updateStatisticsOfGame:game];
        PSAggregationStatistics *agg = [game.statistics trickingOfPlayer:0 forContract:tricks];
        XCTAssertEqual(tricks, agg.tricks);
        XCTAssertEqual(1, agg.sets);
        XCTAssertEqual(1, agg.success);
        XCTAssertEqual(1, [game.statistics passes][1]);
        XCTAssertEqual(1, [game.statistics passes][2]);
    }
}

-(void)testTrickingWithoutThreeScorer {
    PSScorer *scorer = [PSScorer trickingWithoutThreeScorer];
    PSCalculator *calc = [PSCalculator calculatorForGameFlavor:kGameFlavorSochi];
    for (PSTrickCount tricks = PS_BID_MIN ; tricks <= PS_BID_MAX ; tricks++) {
        game = [[PSGame alloc] init];
        game.player.declarer = 0;
        [game setCurrentBid:[PSBid bidPlayWithTrump:kSuitClubs tricks:tricks] forPlayer:0];
        [scorer updateGame:game withSettings:settings andCalculator:calc];
        XCTAssertEqual(6*(PSGetContractIndex(tricks)+1), [game scoreForPlayerId:0].mountain.current);
        [scorer updateStatisticsOfGame:game];
        XCTAssertEqual([game.statistics withoutThree][0], 1);
        PSAggregationStatistics *agg = [game.statistics trickingOfPlayer:0 forContract:tricks];
        XCTAssertEqual(tricks-3, agg.tricks);
        XCTAssertEqual(1, agg.sets);
        XCTAssertEqual(0, agg.success);
    }
}

-(void)testBalanceGame {
    PSScorer *scorer = [PSScorer trickingScorer];
    PSCalculator *calc = [PSCalculator calculatorForGameFlavor:kGameFlavorSochi];
    game = [[PSGame alloc] init];
    game.player.declarer = 0;
    game.whisters = 2;
    [game setCurrentBid:[PSBid bidPlayWithTrump:kSuitClubs tricks:PS_BID_MIN] forPlayer:0];
    [game setCurrentBid:[PSBid bidWhist] forPlayer:1];
    [game setCurrentBid:[PSBid bidWhist] forPlayer:2];
    game.player.tricks[0] = PS_BID_MIN;
    game.player.tricks[1] = (PS_BID_MAX-PS_BID_MIN)/2;
    game.player.tricks[2] = (PS_BID_MAX-PS_BID_MIN)/2;
    [scorer scoreGame:game withSettings:settings andCalculator:calc];
    XCTAssertEqual([game scoreForPlayerId:0].balance.current, 4);
    XCTAssertEqual([game scoreForPlayerId:1].balance.current, -2);
    XCTAssertEqual([game scoreForPlayerId:2].balance.current, -2);
}

@end
